﻿using Lhz.InternetService;
using Lhz.Sc.Entity.Database;
using Lhz.Sc.Entity.Exception;
using Lhz.Sc.Entity.Interface;
using Lhz.Serialization;
using System;
using System.Linq;
using System.Transactions;

namespace Lhz.Sc.Business
{
    public class ServiceCenterOperation
    {
        private ServiceInvoker si;
        private JsonSerializer js = new JsonSerializer();
        private LogProxy lp = new LogProxy();

        public ServiceCenterOperation(ServiceInvoker si = null)
        {
            if (si == null)
            {
                this.si = new ServiceInvoker();
            }
            else
            {
                this.si = si;
            }
        }

        public void Login(string username, string password, Action<User> saveLogin = null)
        {
            var uo = new UserProxy();
            var user = uo.QueryByUsernameAndPassword(username, password);
            if (user == null)
            {
                throw new UsernameOrPasswordErrorException();
            }
            else
            {
                if (saveLogin != null)
                {
                    saveLogin(user);
                }
            }
        }

        private object Invoke(Service service, Operation operation, object[] arguments)
        {
            return si.Invoke(service.Url, operation.Name, arguments);
        }

        private void CheckAuthorization(InvokeArgument invokeArgument, User user, out Service service, out Operation operation)
        {
            var applicationNo = invokeArgument.ApplicationNo;
            var serviceNo = invokeArgument.ServiceNo;
            var operationNo = invokeArgument.OperationNo;
            var ap = new ApplicationProxy();
            var application = ap.QueryByNo(applicationNo);
            if (application == null)
            {
                throw new ApplicationNoNotFoundException(applicationNo);
            }
            var sp = new ServiceProxy();
            service = sp.QueryByApplicationNoAndServiceNo(applicationNo, serviceNo);
            if (service == null)
            {
                throw new ServiceNoNotFoundException(applicationNo, serviceNo);
            }
            operation = service.Operations.Where(r => r.No == operationNo).FirstOrDefault();
            if (operation == null)
            {
                throw new OperationNoNotFoundException(applicationNo, serviceNo, operationNo);
            }
            if (!new AuthorizationProxy().Check(application, service, operation, user.Guid))
            {
                throw new NotAuthorizationException(applicationNo, serviceNo, operationNo);
            }
        }

        private object[] ConvertJsonArgumentsToArguments(Type[] types, string[] jsonArguments)
        {
            if (types.Length != (jsonArguments == null ? 0 : jsonArguments.Length))
            {
                throw new ServiceCenterException("参数个数【" + jsonArguments.Length + "】与服务需要的参数个数【" + types.Length + "】不符。");
            }
            object[] arguments = null;
            if (jsonArguments == null)
            {
                arguments = new object[0];
            }
            else
            {
                arguments = new object[jsonArguments.Length];
                for (int i = 0; i < jsonArguments.Length; i++)
                {
                    if (jsonArguments[i] != null)
                    {
                        if (types[i].IsByRef)
                        {
                            types[i] = types[i].GetElementType();
                        }
                        arguments[i] = js.Deserialize(types[i], jsonArguments[i]);
                    }
                }
            }
            return arguments;
        }

        private string[] ConvertArgumentsToJsonArguments(object[] arguments)
        {
            return arguments.Select(r => js.Serialize(r)).ToArray();
        }

        public InvokeResult JsonInvoke(InvokeArgument invokeArgument, User user, string clientIp)
        {
            var log = new Log()
            {
                InvokeDatetime = DateTime.Now,
                Ip = clientIp,
                UserGuid = user.Guid,
                User = user,
                IsSuccess = false
            };
            var invokeResult = new InvokeResult()
            {
                ApplicationNo = invokeArgument.ApplicationNo,
                ServiceNo = invokeArgument.ServiceNo,
                OperationNo = invokeArgument.OperationNo,
                Arguments = invokeArgument.Arguments,
                IsSuccess = false
            };
            try
            {
                Service service;
                Operation operation;
                CheckAuthorization(invokeArgument, user, out service, out operation);
                var types = si.GetMethodParameterTypes(service.Url, operation.Name);
                var arguments = ConvertJsonArgumentsToArguments(types, invokeArgument.Arguments);
                using (var ts = new TransactionScope())
                {
                    var ir = Invoke(service, operation, arguments);
                    if (ir != null)
                    {
                        invokeResult.Result = js.Serialize(ir);
                    }
                    invokeResult.IsSuccess = true;
                    invokeResult.Arguments = ConvertArgumentsToJsonArguments(arguments);
                    log.IsSuccess = true;
                    ts.Complete();
                }
                return invokeResult;
            }
            catch (Exception e)
            {
                invokeResult.Result = js.Serialize(e);
                throw new InvokeException(invokeArgument.ApplicationNo, invokeArgument.ServiceNo, invokeArgument.OperationNo, e);
            }
            finally
            {
                log.InvokeResult = js.Serialize(invokeResult);
                log.CompleteDatetime = DateTime.Now;
                lp.Add(log);
            }
        }

        public InvokeResult[] JsonInvoke(User user, string clientIp, InvokeArgument[] invokeArguments)
        {
            var invokeResults = new InvokeResult[invokeArguments.Length];
            var log = new Log()
            {
                InvokeDatetime = DateTime.Now,
                Ip = clientIp,
                UserGuid = user.Guid,
                User = user,
                IsSuccess = false
            };
            Exception exception = null;
            using (var ts = new TransactionScope())
            {
                var isSuccess = true;
                for (int i = 0; i < invokeArguments.Length; i++)
                {
                    var invokeArgument = invokeArguments[i];
                    var invokeResult = new InvokeResult()
                    {
                        ApplicationNo = invokeArgument.ApplicationNo,
                        ServiceNo = invokeArgument.ServiceNo,
                        OperationNo = invokeArgument.OperationNo,
                        Arguments = invokeArgument.Arguments,
                        IsSuccess = false
                    };
                    try
                    {
                        Service service;
                        Operation operation;
                        CheckAuthorization(invokeArgument, user, out service, out operation);
                        var types = si.GetMethodParameterTypes(service.Url, operation.Name);
                        var arguments = ConvertJsonArgumentsToArguments(types, invokeArgument.Arguments);
                        var ir = Invoke(service, operation, arguments);
                        if (ir != null)
                        {
                            invokeResult.Result = js.Serialize(ir);
                        }
                        invokeResult.IsSuccess = true;
                        invokeResult.Arguments = ConvertArgumentsToJsonArguments(arguments);
                        invokeResults[i] = invokeResult;
                    }
                    catch (Exception e)
                    {
                        exception = e;
                        invokeResult.Result = js.Serialize(e);
                        isSuccess = false;
                        break;
                    }
                }
                if (isSuccess)
                {
                    log.IsSuccess = true;
                    ts.Complete();
                }
            }

            log.CompleteDatetime = DateTime.Now;
            log.InvokeResult = js.Serialize(invokeResults);
            lp.Add(log);
            if (log.IsSuccess)
            {
                return invokeResults;
            }
            else
            {
                throw new InvokeException(invokeArguments.Select(r => r.ApplicationNo), invokeArguments.Select(r => r.ServiceNo), invokeArguments.Select(r => r.OperationNo), exception);
            }
        }

        public Guid GetGuid()
        {
            return Guid.NewGuid();
        }

        public Guid[] GetGuids(int count)
        {
            var result = new Guid[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = Guid.NewGuid();
            }
            return result;
        }
    }
}
